<!DOCTYPE HTML>
<html>
    <head>
        <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <link rel="stylesheet" href="https://unpkg.com/turretcss@5.2.1/dist/turretcss.min.css">
        <link rel="stylesheet" href="style.css">
    </head>

    <body id="main">

        <nav class="nav-inline background-light-100 box-shadow-xxl">
            <ul>
                <li><a href="/" class="brand">
                        <img class="logo" src="/doc/favicon/apple-touch-icon.png" />
                        <span>Cotonic</span>
                    </a>
                </li>
                <li>
                    <span>Chat Example</span>
                </li>
            </ul>
        </nav>

        <main style="padding: 30px;">

        <div class="container">
            <p>
            Chat with any tab connected to the same broker and mqtt topic.
            </p>
            
            <p>
            This example connects the page to <a href="https://mqtt.eclipse.org"><code>mqtt.eclipse.org</code></a>,
            a public, test MQTT broker service. And subscribes to topic <code>cotonic/chat</code>. Chat messages
            which are published on this topic will be displayed in the chat box.
            </p>

            <p>
            You can open <a href="." target="_blank">another tab</a> and chat with yourself if nobody is online.
            Or examine the 
            <a href="https://github.com/cotonic/cotonic/tree/master/examples/chat" target="chat_example_code">code</a>
            </a>

            <p>
            <hr />
            </p>
        </div>

        <div class="chat-box box-shadow-xxl">
            <header class="background-primary-200 padding-xs">
                <!-- publish the data of the form on local topic "chat/nick" --> 
                <form data-onsubmit-topic="chat/nick" data-onsubmit-cancel="preventDefault">
                    <div class="input-group">
                        <input id="nick" name="nick" value="" />
                        <button class="button button-primary" type="submit">Nickname</button>
                    </div>
                </form>
            </header>

            <div id="bubble-container" class="padding-xs">
            </div>

            <footer class="background-primary-200 padding-xs">
                <!-- publish the data of the form on local topic "chat/message" --> 
                <form data-onsubmit-topic="chat/message"
                      data-onsubmit-reset
                      data-onsubmit-cancel="preventDefault">

                    <div class="input-group">
                        <input name="message" placeholder="Type a message.." />
                        <button type="submit" class="button button-primary">Send</button>
                    </div>
                </form>
            </footer>
        </div>

        <script type="text/javascript" src="/cotonic.js" data-base-worker-src="/cotonic-worker.js"></script>

    <script type="text/javascript">
        const messages = [];
        const self_id = Math.floor(Math.random() * 1000000);

        const broker = "mqtt.eclipse.org";
        const broker_path = "/mqtt";

        const presetNicks = ["Joe", "Mike", "Robert", "Micky", "Maus", "Donald", "Bruce", "Wayne", "Clark",
            "Kent", "Sarah", "Connor", "Mary", "Shelley", "Rosemary", "Wilma", "Louis", "Selina",
            "Barbara", "Gordon", "Herbert", "The Count"];

        let nick = presetNicks[Math.floor(Math.random() * presetNicks.length)];
        document.getElementById("nick").setAttribute("value", nick);

        /**
         * Register the bubble-container with the user interface composer.
         * See: https://cotonic.org/#model.ui
         */
        cotonic.broker.publish("model/ui/insert/bubble-container", {
            priority: 10,
            initialData: view(),
            inner: true
        });

        /**
         * Update the view. Publishes the updated bubble view, and scrolls
         * it to the bottom.
         */
        function updateView() {
            cotonic.broker.publish("model/ui/update/bubble-container", view());
        }

        /**
         * Publish a system messages. The will be displayed centered in the 
         * chat.
         */
        function systemMessage(msg) {
            messages.push({user_id: "$sys", msg: msg});
            updateView();
        }

        /**
         * Scroll the messages to the bottom when the view is updated. 
         */
        cotonic.broker.subscribe("model/ui/event/dom-updated/bubble-container",
            function(m) {
                const c = document.getElementById("bubble-container");
                c.scrollTop = c.scrollHeight
            });

        /**
         * Subscribe to the local topic "chat/nick", When the user 
         * submits the form to change the nickname, it will save
         * the nickname in the local state of the chat.
         * The new nickname will be used for the next chat message.
         */
        cotonic.broker.subscribe("chat/nick", function(m) {
            nick = m.payload.value.nick;
        });

        /**
         * Subscribe to the local topic "chat/message". When the user
         * presses sent, the data in the form will be published on
         * this topic, it will then be re-published on the bridge.
         * When the bridge is connected, this message will be published
         * on the remote broker.
         */
        cotonic.broker.subscribe("chat/message", function(m) {
            const msg = m.payload.value.message;

            cotonic.broker.publish("bridge/broker/cotonic/chat",
                {msg: msg, user_id: self_id, nick: nick});
        })

        /**
         * Subscribe to the broker status. Its status will be published on a topic.
         * Sends system messages to indicate to the user what state the bridge has.
         */
        cotonic.broker.subscribe("$bridge/broker/status", function(m) {
            if(m.payload.hasOwnProperty("is_connected")) {
                systemMessage(
                    m.payload.is_connected ? "You are now connected, and subscribed to topic \"cotonic/chat\"."
                                           : "Currently disconnected.");
            }
        })

        /**
         * Send a system message we are going to connect to the broker
         * The option 'is_ui_state' tells the bridge to publish its state to the ui model so that on the html tag
         * the 'ui-state-bridge-connected' class and the attribute data-ui-state-bridge-connection is set.
         */
        systemMessage("Connecting to " + broker);
        cotonic.mqtt_bridge.newBridge(broker, { name: 'broker', protocol: "wss", controller_path: broker_path, is_ui_state: true });

        /**
         * Subscribe to the "cotonic/chat" topic via the broker. All messages, including your own
         * will be received here. The message will be added to the local chat state, and the
         * view will be updated.
         */
        cotonic.broker.subscribe("bridge/broker/cotonic/chat",
            function(msg, inf) {
                /*
                 * Filter out messages from the local broker. We want messages from
                 * the remote broker only. These have the packet_id property set, but
                 * it could be null.
                 */
                if(!msg.hasOwnProperty("packet_id")) {
                    if(!msg.payload.msg) return;

                    messages.push(msg.payload);
                    updateView();
                    return;
                }

                let p = msg.payload;

                // Check if this is a chat message.
                if(!(p.nick && p.msg))
                    return;

                // When it is a message from ourself, don't display it
                // the message was already inserted.
                if(p.user_id === self_id)
                    return;

                messages.push(p);
                updateView();
            });

        /**
         * Create the message view. Produces an array of tokens which can be used
         * by the user interface composer.
         */
        function view() {
            const tokens = [];

            let current_id = null;

            const div = {type: "open", tag: "div"}
            const close_div = {type: "close", tag: "div"}

            const container_self = {type: "open", tag: "div", attributes: ["class", "bubble-container self"]};
            const group_self = {type: "open", tag: "div", attributes: ["class", "bubble-group self"]};

            const container_other = {type: "open", tag: "div", attributes: ["class", "bubble-container other"]};
            const group_other = {type: "open", tag: "div", attributes: ["class", "bubble-group other"]};

            const container_sys = {type: "open", tag: "div", attributes: ["class", "bubble-container system"]};
            const group_sys = {type: "open", tag: "div", attributes: ["class", "bubble-group system"]};

            const close_span = {type: "close", tag: "span"};

            for(let i = 0; i < messages.length; i++) {
                const m = messages[i];

                if(m.user_id !== current_id) {
                    if(current_id !== null) {
                        // close the current container
                        tokens.push(close_div);
                        tokens.push(close_div);
                    }

                    if(m.user_id === self_id) {
                        tokens.push(container_self);
                        tokens.push(group_self);
                    } else if(m.user_id === "$sys") {
                        tokens.push(container_sys);
                        tokens.push(group_sys);
                    } else {
                        tokens.push(container_other);
                        tokens.push(group_other);
                        tokens.push(div);
                            tokens.push({type: "open", tag: "span", attributes: ["class", "bubble-name"]});
                            tokens.push({type: "text", data: m.nick});
                            tokens.push(close_span);
                        tokens.push(close_div);
                    }

                    current_id = m.user_id;
                }

                if(m.user_id === self_id) {
                    tokens.push({type: "open", tag: "div", attributes: ["class", "bubble self"]});
                } else if (m.user_id === "$sys") {
                    tokens.push({type: "open", tag: "div", attributes: ["class", "bubble system"]});
                } else {
                    tokens.push({type: "open", tag: "div", attributes: ["class", "bubble other"]});
                }
                    tokens.push({type: "open", tag: "span"});
                    tokens.push({type: "text", data: m.msg});
                    tokens.push(close_span);
                tokens.push(close_div);
            }

            return tokens;
        }
    </script>
    </body>
</html>
